import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicInteger;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;

// java --add-opens java.base/jdk.internal.vm=ALL-UNNAMED LoomBug
public final class LoomBug {
	private static final ContinuationScope cs = new ContinuationScope("VirtualThreads");
	private static final ForkJoinPool pool = new ForkJoinPool();
	private static final ConcurrentLinkedQueue<CoTask> queue = new ConcurrentLinkedQueue<>();
	private static final AtomicInteger sum = new AtomicInteger();
	private static final AtomicInteger finish = new AtomicInteger();
	private static final AtomicInteger a = new AtomicInteger();

	static final class CoTask extends Continuation {
		private final Runnable task;

		private CoTask(Runnable target) {
			super(cs, target);
			task = () -> {
				run();
				if (!isDone())
					queue.offer(this);
			};
		}
	}

	static void go(Runnable r) {
		pool.submit(new CoTask(r).task);
	}

	static void executeQueue() {
		for (int i = 0; i < 100; i++) {
			var coTask = queue.poll();
			if (coTask != null)
				pool.submit(coTask.task);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		go(() -> {
			//noinspection InfiniteLoopStatement
			for (; ; ) {
				for (int i = 0; i < 100; i++) {
					var coTask = queue.poll();
					if (coTask != null)
						pool.submit(coTask.task);
				}
				try {
					//noinspection BusyWait
					Thread.sleep(10);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		});
		var n = pool.getParallelism();
		for (int i = 0; i < n; i++) {
			go(() -> {
				for (int j = 0; j < 1000; j++) {
					synchronized (LoomBug.class) {
						if (a.get() > 0)
							System.out.println("ERR");
						a.incrementAndGet();
						Continuation.getCurrentContinuation(cs);
						try {
							Thread.sleep(1);
						} catch (InterruptedException e) {
							throw new RuntimeException(e);
						}
						a.decrementAndGet();
					}
					// executeQueue();
					Continuation.yield(cs);
					sum.incrementAndGet();
				}
				finish.incrementAndGet();
			});
		}
		for (; ; ) {
			System.out.println(sum.get() + ", " + finish.get() + ", " + a.get());
			if (sum.get() == n * 1000) {
				System.out.println("done!");
				System.exit(0);
			}
			//noinspection BusyWait
			Thread.sleep(1000);
		}
	}
}
